use std::fs;
use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
struct Coord {
    x: i32,
    y: i32,
}

impl Coord {
    pub fn new(x: i32, y: i32) -> Coord {
        Coord { x, y }
    }
}

#[derive(Debug)]
struct Line {
    begin: Coord,
    end: Coord,
}

impl Line {
    pub fn is_horizontal(&self) -> bool {
        self.begin.x != self.end.x && self.begin.y == self.end.y
    }

    pub fn is_vertical(&self) -> bool {
        self.begin.x == self.end.x && self.begin.y != self.end.y
    }

    pub fn is_diagonal(&self) -> bool {
        let delta_x = (self.begin.x - self.end.x).abs();
        let delta_y = (self.begin.y - self.end.y).abs();
        delta_x == delta_y
    }

    pub fn coords(&self) -> Vec<Coord> {
        if self.is_horizontal() {
            let y = self.begin.y;
            let min_x = std::cmp::min(self.begin.x, self.end.x);
            let max_x = std::cmp::max(self.begin.x, self.end.x);
            (min_x..=max_x).into_iter().map(|x| Coord::new(x, y)).collect()

        } else if self.is_vertical() {
            let x = self.begin.x;
            let min_y = std::cmp::min(self.begin.y, self.end.y);
            let max_y = std::cmp::max(self.begin.y, self.end.y);
            (min_y..=max_y).into_iter().map(|y| Coord::new(x, y)).collect()

        } else if self.is_diagonal() {
            let mut coords: Vec<Coord> = Vec::new();
            let step_x = if self.begin.x < self.end.x { 1 } else { -1 };
            let step_y = if self.begin.y < self.end.y { 1 } else { -1 };

            let mut coord = Coord::new(self.begin.x, self.begin.y);
            for _ in 0..=(self.begin.x - self.end.x).abs() {
                coords.push(coord);
                coord.x += step_x;
                coord.y += step_y;
            }

            coords

        } else {
            Vec::new()
        }
    }
}

impl From<&str> for Line {
    // 0,9 -> 5,9
    fn from(value: &str) -> Self {
        fn parse_coord(s: &str) -> Coord {
            let index = s.find(',').unwrap();
            Coord {
                x: (&s[..index]).parse().unwrap(),
                y: (&s[index+1..]).parse().unwrap(),
            }
        }

        let index = value.find(" -> ").unwrap();
        Line {
            begin: parse_coord(&value[..index]),
            end:   parse_coord(&value[(index + 4)..])
        }
    }
}

fn parse_input() -> Vec<Line> {
    let input = fs::read_to_string("./day05/input.txt").unwrap();
    input.lines().map(|line| line.into()).collect()
}

#[allow(unused)]
fn part1(lines: Vec<Line>) {
    let lines: Vec<Line> = lines.into_iter().filter(|line| line.is_horizontal() || line.is_vertical()).collect();

    let mut counter: HashMap<Coord, usize> = HashMap::new();
    for line in lines {
        for coord in line.coords().into_iter() {
            let count = counter.entry(coord).or_insert(0);
            *count += 1;
        }
    }
    
    let result = counter.into_values().filter(|&count| count >= 2).count();
    println!("Part1: {}", result);
}

#[allow(unused)]
fn part2(lines: Vec<Line>) {
    let mut counter: HashMap<Coord, usize> = HashMap::new();
    for line in lines {
        for coord in line.coords().into_iter() {
            let count = counter.entry(coord).or_insert(0);
            *count += 1;
        }
    }
    
    let result = counter.into_values().filter(|&count| count >= 2).count();
    println!("Part2: {}", result);
}

fn main() {
    part1(parse_input());
    part2(parse_input());
}
